import { BadRequestException, Inject, Injectable } from '@nestjs/common';
import { createMathExpr } from 'svg-captcha';
import * as http from 'http';
import { stringify } from 'qs';
import { ConfigService } from '@nestjs/config';
import { UserService } from 'src/user/user.service';
import { decrypt, encrypt, getRedisKey, isAdmin, toEndTodaySeconds } from 'src/utils';
import { CACHE_MANAGER } from '@nestjs/cache-manager';
import { Cache } from 'cache-manager';
import { IDENTITY, IDENTITY_MAP, SMS_TYPE, UPDATE_TYPE } from 'src/utils/config';
import { IsNull } from 'typeorm';
const nodemailer = require('nodemailer');

@Injectable()
export class SmsCaptchaService {
    constructor(
        @Inject(CACHE_MANAGER) private cacheManager: Cache,
        private configService: ConfigService,
        private userService: UserService,
    ) { }

    /**
     * 创建图形验证码
     *
     * @return {*} {data, text} data:图片地址，text:答案
     * @memberof SmsCaptchaService
     */
    createExpr(): any {
        const exprConfig = {
            size: 5,
            ignoreChars: '0o1iIl',
            noise: 5,
            color: true,
            background: '#E5FFF3',
            fontSize: 56,
            mathMin: 1,
            mathMax: 33,
            // mathOperator: "+-",
        };

        return createMathExpr(exprConfig);
    }

    /**
     * 获取短信验证码状态描述
     *
     * @param {string} result 短信验证码状态码
     * @return {*} 状态描述
     * @memberof SmsCaptchaService
     */
    statusStr(result: string): string {
        if ('0' === result) return '';
        if ('-1' === result) return `参数不全`;
        if ('-2' === result) return `服务器空间不支持,请确认支持curl或者fsocket，联系您的空间商解决或者更换空间！`;
        if ('30' === result) return `密码错误`;
        if ('40' === result) return `账户不存在`;
        if ('41' === result) return `余额不足`;
        if ('42' === result) return `账户已过期`;
        if ('43' === result) return `IP地址限制`;
        if ('50' === result) return `内容含有敏感字`;
        if ('51' === result) return `手机号码不正确`;

        return '未知异常，请联系管理员';
    }

    /**
     * 获取手机、邮箱账号，用来发送验证码
     *
     * @param {string} user_id 用户id
     * @param {*} req req.query 请求参数
     * @return {*} 手机号或邮箱账号
     * @memberof SmsCaptchaService
     */
    async getSmsAccount(user_id: string, req: any): Promise<string> {
        // 基础参数已经校验完毕，这里就不做额外的校验
        const { phone, email, identity, ...rest } = req || {};
        const isIdentity = IDENTITY.includes(identity); // 身份验证请求

        if (isIdentity) {
            // 身份认证
            if (!user_id) throw new BadRequestException(`无效请求`);
            const userInfo = await this.userService.findUser({ id: user_id });
            if (!userInfo) throw new BadRequestException(`无效请求`);

            return decrypt(userInfo[identity]);
        }

        return phone || email;
    }

    /**
     * 发送手机验证码
     *
     * @param {string} phone 需要发送短信的手机号码
     * @return {*} 验证码
     * @memberof SmsCaptchaService
     */
    async createSms(phone: string): Promise<string> {
        return Promise.resolve('123456'); // 测试环境，直接返回固定验证码

        const code = ((Math.random() * 900000 + 100000) | 0).toString();
        const smsApi = 'api.smsbao.com';
        const user = this.configService.get('SMS_USER'); // 短信平台账号
        const pass = this.configService.get('SMS_AK'); // apikey，这里不用md5后的短信平台密码
        const content = `${this.configService.get('WEB_NAME')}来信：您的验证码是${code}。如非本人操作，请忽略本短信`; // 要发送的短信内容
        const userExist = await this.userService.findUser({
            phone: encrypt(phone),
            deleted_at: IsNull(),
        });
        const autoRegister = !!this.configService.get('AUTO_REGISTER');

        if (!userExist && !autoRegister) throw new BadRequestException(`该手机号未注册，请先注册`);

        const data = {
            u: user,
            p: pass,
            m: phone,
            c: content,
        };
        const query = stringify(data);
        const options = {
            hostname: smsApi,
            path: '/sms?' + query,
            method: 'GET',
        };

        const that = this;
        return new Promise((resolve, reject) => {
            if (userExist?.id && isAdmin(userExist?.id)) return resolve('123456');

            const req = http.request(options, function (res) {
                res.setEncoding('utf-8');
                let data = '';
                res.on('data', function (result) {
                    data += result;
                });
                res.on('end', function () {
                    if ('0' === data) resolve(code);
                    reject(that.statusStr(data));
                });
            });
            req.on('error', function (err) {
                reject(err?.message);
            });
            req.end();
        });
    }

    /**
     * 检查验证码是否正确，不正确就累计次数
     *
     * @param {string} smsCode 验证码
     * @param {string} account 发送账号
     * @param {string} slug 类型
     * @return {*}
     * @memberof SmsCaptchaService
     */
    async checkSmsCode(smsCode: string, account: string, slug: string): Promise<boolean> {
        const redisKey = getRedisKey(account, slug, 'sms');
        const redisSmsValue = await this.cacheManager.store.get(redisKey);
        const errorCountKey = getRedisKey(account, slug, 'err_count');
        const errorCount: number = (await this.cacheManager.store.get(errorCountKey)) || 0;

        if (!smsCode) throw new BadRequestException(`验证码不能为空`);

        // 距离24点的秒数
        const leftSeconds = toEndTodaySeconds();
        // Redis 有对应的值，但是和用户传递的不一致，
        if (redisSmsValue && smsCode !== redisSmsValue) {
            // 累计错误次数
            await this.cacheManager.store.set(errorCountKey, errorCount + 1, 1000 * leftSeconds);
            throw new BadRequestException(`验证码错误`);
        }

        return true;
    }
    /**
     * 标记认证类型
     *
     * @param {string} user_id 用户id
     * @param {string} slug 类型
     * @return {*}
     * @memberof SmsCaptchaService
     */
    async markVerifyIdentity(user_id: string, slug: string): Promise<any> {
        if (!UPDATE_TYPE.includes(slug)) throw new BadRequestException(`无效类型`);

        const verifiedKey = getRedisKey(user_id, slug, 'verified');
        const smsTtl = this.configService.get('SMS_EXPIRE');
        await this.cacheManager.store.set(verifiedKey, true, smsTtl);
    }

    /**
     * 频率检测，3分钟内3次
     *
     * 只做校验，不做设置
     * @param {string} account 账号
     * @param {string} slug 类型
     * @param {number} [maxAttempts=3] 最大频率次数
     * @param {number} [ttl=180] 时间范围，单位秒
     * @return {*}  {Promise<boolean>}
     * @memberof SmsCaptchaService
     */
    async rateCheck(account: string, slug: string, maxAttempts: number = 3, ttl: number = 180): Promise<boolean> {
        const smsTtl = this.configService.get('SMS_EXPIRE') / (60 * 1000);
        const limitKey = getRedisKey(account, slug, 'limit');
        const keyCount: number = (await this.cacheManager.store.get(limitKey)) || 0;

        const smsKey = getRedisKey(account, slug, 'sms');
        const redisSms = await this.cacheManager.store.get(smsKey);
        if (redisSms) throw new BadRequestException(`验证码${smsTtl}分钟内有效，请勿重复发送`);

        return keyCount + 1 <= maxAttempts;
    }
    /**
     * 冻结检测
     *
     * @param {string} account 账号
     * @param {string} slug 类型
     * @return {*}  {Promise<boolean>}
     * @memberof SmsCaptchaService
     */
    async frozenCheck(account: string, slug: string): Promise<any> {
        const smsErrorLimit = this.configService.get('SMS_FREEZE_LIMIT');
        const errorCountKey = getRedisKey(account, slug, 'err_count');
        const errorCount: number = await this.cacheManager.store.get(errorCountKey) || 0;

        if (errorCount > smsErrorLimit) {
            const ttl = await this.cacheManager.store.ttl(errorCountKey);
            const ttlSec = Math.floor(ttl / 1000);
            const h = Math.floor(ttlSec / 3600);
            const m = Math.floor((ttlSec % 3600) / 60);

            throw new BadRequestException(`${account}已被冻结，停止发送验证码，${h}时${m}分后解冻；或联系客服解冻`);
        }
    }
    /**
     * 是否应该使用图形验证码
     *
     * @param {string} account 账号
     * @param {string} slug 类型
     * @return {*}  {Promise<boolean>}
     * @memberof SmsCaptchaService
     */
    async useCaptchaCheck(account: string, slug: string): Promise<boolean> {
        const errorCountKey = getRedisKey(account, slug, 'err_count');
        const errorCount: number = await this.cacheManager.store.get(errorCountKey) || 0;

        return 0 !== errorCount;
    }
    /**
     * 是否需要图片验证码
     *
     * @param {string} account 账号
     * @param {string} slug 类型
     * @param {string} captcha 图形验证码
     * @return {*}  {Promise<boolean>}
     * @memberof SmsCaptchaService
     */
    async captchaCheck(account: string, slug: string, captcha: string): Promise<boolean> {
        const captchaRedisKey = getRedisKey(account, slug, 'captcha');

        // 判断手机号和图形验证码是否匹配
        const redisCaptcha = await this.cacheManager.store.get(captchaRedisKey);
        if (!redisCaptcha || captcha.toString() !== redisCaptcha.toString()) throw new BadRequestException(`图形验证码错误`); // 前端携带图形验证码，但redis里并没有存对应的值

        return true;
    }
    /**
     * 检查身份是否验证通过
     *
     * @param {string} user_id 用户id
     * @param {string} slug 类型
     * @return {*}
     * @memberof SmsCaptchaService
     */
    async identityCheck(user_id: string, slug: string): Promise<any> {
        const verifiedKey = getRedisKey(user_id, IDENTITY_MAP[slug], 'verified');
        const verifyOk = await this.cacheManager.store.get(verifiedKey);

        if (!verifyOk) throw new BadRequestException(`身份验证已过期，请重新验证`);

        return !!verifyOk;
    }
}
